use std::{collections::HashMap, path::Path};

use actix_files::NamedFile;
use actix_multipart::form::{MultipartForm, tempfile::TempFile, text::Text};
use actix_web::{
    HttpMessage, HttpRequest, HttpResponse, Responder, get, post, put,
    web::{self},
};
use base64::{Engine, prelude::BASE64_STANDARD};
use chrono::{DateTime, Utc};
use futures_util::StreamExt;
use hmac::Mac;
use tokio::{
    fs::{OpenOptions, create_dir_all},
    io::AsyncWriteExt,
};

use crate::{
    AppState,
    model::{App, Bucket},
    service::{self, auth},
};

#[derive(Debug, MultipartForm)]
struct UploadForm {
    #[multipart(rename = "OSSAccessKeyId")]
    pub oss_access_key_id: Text<String>,
    pub policy: Text<String>,
    pub signature: Text<String>,
    pub key: Text<String>,
    pub file: TempFile,
}

#[get("/{key:.*}")]
async fn get_object(req: HttpRequest, app_state: web::Data<AppState>) -> impl Responder {
    let headers = req.headers();
    let host = headers.get("host").unwrap().to_str().unwrap();
    let mut bucket_model: Option<Bucket> = None;
    let mut app_model: Option<App> = None;

    if let Some(bucket) = service::bucket::find_by_domain(&app_state.database_connection, host)
        .await
        .unwrap()
    {
        bucket_model = Some(bucket.clone());

        if let Some(app) = service::app::find_by_id(&app_state.database_connection, bucket.app_id)
            .await
            .unwrap()
        {
            app_model = Some(app.clone())
        }
    } else {
        if host.find(".").is_none() {
            return HttpResponse::NotFound().body("");
        }

        let bucket_name = host.get(0..host.find(".").unwrap()).unwrap();

        if let Some(bucket) =
            service::bucket::find_by_name(&app_state.database_connection, bucket_name)
                .await
                .unwrap()
        {
            bucket_model = Some(bucket.clone());

            if let Some(app) =
                service::app::find_by_id(&app_state.database_connection, bucket.app_id)
                    .await
                    .unwrap()
            {
                app_model = Some(app.clone())
            }
        }
    }

    if bucket_model.is_none() || app_model.is_none() {
        return HttpResponse::NotFound().body("");
    }

    let bucket_ref = &bucket_model.unwrap();
    let key: String = req.match_info().get("key").unwrap().parse().unwrap();
    let key_decode = percent_encoding::percent_decode_str(&key)
        .decode_utf8_lossy()
        .into_owned();

    if bucket_ref.bucket_type == 2 {
        let raw_query = req.query_string();
        let params: web::Query<HashMap<String, String>> =
            web::Query::from_query(raw_query).unwrap();

        if !params.contains_key("Expires")
            || !params.contains_key("OSSAccessKeyId")
            || !params.contains_key("Signature")
        {
            return HttpResponse::Forbidden().body("");
        }

        let expires = params
            .get("Expires")
            .unwrap()
            .parse::<i64>()
            .unwrap_or_default();

        let expires_date = DateTime::from_timestamp(expires, 0).unwrap();

        if expires_date < Utc::now() {
            return HttpResponse::Forbidden().body("签名已失效");
        }

        let signature = params.get("Signature").unwrap();

        let resource = format!("/{}/{}", bucket_ref.name, key);
        let sign_str = format!("GET\n{}\n{}\n{}\n{}{}", "", "", expires, "", resource);

        let mut mac = hmac::Hmac::<sha1::Sha1>::new_from_slice(
            app_model.unwrap().access_key_secret.as_bytes(),
        )
        .unwrap();

        mac.update(sign_str.as_bytes());

        let signed_str = BASE64_STANDARD.encode(mac.finalize().into_bytes());

        if signature.to_string() != signed_str {
            return HttpResponse::Forbidden().body("");
        }
    }

    let file_path = Path::new(&app_state.configuration.oss_local_storage_path())
        .join(bucket_ref.name.clone())
        .join(&key_decode);

    if file_path.exists() {
        let file = NamedFile::open_async(file_path).await.unwrap();

        file.into_response(&req)
    } else {
        HttpResponse::NotFound().body("")
    }
}

#[put("/{key}")]
async fn put_object(
    req: HttpRequest,
    mut body: web::Payload,
    app_state: web::Data<AppState>,
) -> impl Responder {
    let key: String = req.match_info().get("key").unwrap().parse().unwrap();
    let key_decode = percent_encoding::percent_decode_str(&key)
        .decode_utf8_lossy()
        .into_owned();
    let headers = req.headers();

    if let Some(auth_header) = headers.get("Authorization") {
        let auth_value = auth_header.to_str().unwrap();
        let access_key_id = auth_value
            .get("OSS".len() + 1..auth_value.find(":").unwrap())
            .unwrap();

        let app =
            service::app::find_by_access_key_id(&app_state.database_connection, access_key_id)
                .await
                .unwrap()
                .unwrap();

        let host = headers.get("host").unwrap().to_str().unwrap();
        let bucket = host.get(0..host.find(&app.endpoint).unwrap() - 1).unwrap();
        let signature = auth_value
            .get(auth_value.find(":").unwrap() + 1..auth_header.len())
            .unwrap();

        let content_type = req.content_type();
        let date = headers.get("date").unwrap().to_str().unwrap();
        let resource = format!("/{}/{}", bucket, &key_decode);
        let sign_str = service::auth::signature(
            &app.access_key_secret,
            "PUT",
            "",
            content_type,
            date,
            "",
            &resource,
        );

        if sign_str != signature {
            return HttpResponse::Forbidden().body("签名错误");
        }

        let file_path = Path::new(&app_state.configuration.oss_local_storage_path())
            .join(bucket)
            .join(&key_decode);

        if let Some(parent) = std::path::Path::new(&file_path.clone()).parent() {
            create_dir_all(parent).await.unwrap();
        }

        let mut file = OpenOptions::new()
            .append(false)
            .create(true)
            .write(true)
            .open(file_path.clone())
            .await
            .unwrap();

        while let Some(item) = body.next().await {
            let item = item.unwrap();

            file.write(&item.to_vec()).await.unwrap();
        }

        file.flush().await.unwrap();
        file.sync_all().await.unwrap();

        HttpResponse::Ok().body("ok")
    } else {
        HttpResponse::Forbidden().body("")
    }
}

#[post("/")]
async fn upload(
    req: HttpRequest,
    app_state: web::Data<AppState>,
    MultipartForm(form): MultipartForm<UploadForm>,
) -> impl Responder {
    let headers = req.headers();
    let oss_access_key_id = form.oss_access_key_id.as_str();
    let policy = form.policy.as_str();
    let signature = form.signature.as_str();
    let key = form.key.as_str();
    let file = form.file;

    let app =
        service::app::find_by_access_key_id(&app_state.database_connection, oss_access_key_id)
            .await
            .unwrap()
            .unwrap();

    let signed_str = auth::signature_str(&app.access_key_secret, &policy);

    if signature != signed_str {
        return HttpResponse::Forbidden().body("签名错误");
    }

    let bytes = BASE64_STANDARD.decode(policy).unwrap();
    let policy_data: serde_json::Value = serde_json::from_slice(&bytes).unwrap();
    let expiration = policy_data
        .get("expiration")
        .unwrap()
        .as_str()
        .unwrap_or_default();

    let expires_date = DateTime::parse_from_rfc3339(expiration).unwrap();

    if expires_date < Utc::now() {
        return HttpResponse::Forbidden().body("签名已失效");
    }

    let host = headers.get("host").unwrap().to_str().unwrap();
    let mut bucket_model: Option<Bucket> = None;

    if let Some(bucket) = service::bucket::find_by_domain(&app_state.database_connection, host)
        .await
        .unwrap()
    {
        bucket_model = Some(bucket)
    } else {
        let bucket_name = host.get(0..host.find(&app.endpoint).unwrap() - 1).unwrap();

        if let Some(bucket) =
            service::bucket::find_by_name(&app_state.database_connection, bucket_name)
                .await
                .unwrap()
        {
            bucket_model = Some(bucket)
        }
    }

    if bucket_model.is_none() {
        return HttpResponse::Forbidden().body("无效空间");
    }

    let file_path = Path::new(&app_state.configuration.oss_local_storage_path())
        .join(bucket_model.unwrap().name)
        .join(key);

    if let Some(parent) = std::path::Path::new(&file_path.clone()).parent() {
        create_dir_all(parent).await.unwrap();
    }

    file.file.persist(file_path).unwrap();

    HttpResponse::Ok().body("")
}

pub fn routers(cfg: &mut web::ServiceConfig) {
    cfg.service(get_object).service(put_object).service(upload);
}
